const { COMMANDS, SEGMENTS, ARITHMATIC_COMMANDS } = require('./parser.js');
const SEG_TO_RAM = {
  [SEGMENTS.LOCAL]: 'LCL',
  [SEGMENTS.ARGUMENT]: 'ARG',
  [SEGMENTS.TEMP]: 'TEMP',
  [SEGMENTS.THIS]: 'THIS',
  [SEGMENTS.THAT]: 'THAT'
};
const ARITHMATIC_COMMANDS_SIGN = {
  [ARITHMATIC_COMMANDS.ADD]: '+',
  [ARITHMATIC_COMMANDS.SUB]: '-',
  [ARITHMATIC_COMMANDS.NEG]: '-',
  [ARITHMATIC_COMMANDS.EQ]: '=',
  [ARITHMATIC_COMMANDS.GT]: '>',
  [ARITHMATIC_COMMANDS.LT]: '<',
  [ARITHMATIC_COMMANDS.AND]: '&',
  [ARITHMATIC_COMMANDS.OR]: '|',
  [ARITHMATIC_COMMANDS.NOT]: '!'
};

const POINTER_SEG_MAP = {
  0: 'THIS',
  1: 'THAT'
};

class CodeWriter {
  constructor(littleIO) {
    this.littleIO = littleIO;
    this.cache = [];
    this.jumpIndex = 0;
    this.fileName = 'Foo';
    this.returnAddressIndexMap = {};
    this.functionCallStack = [];
  }

  setFileName(fileName) {
    this.fileName = fileName;
  }

  pushFunctionName(functionName) {
    this.functionCallStack.push(functionName);
  }

  _writeBootstrap() {
    
  }

  writeArithmatic(command) {
    this.cache.push(...this._getComment(command));
    let code;
    if (
      command === ARITHMATIC_COMMANDS.ADD ||
      command === ARITHMATIC_COMMANDS.SUB ||
      command === ARITHMATIC_COMMANDS.OR ||
      command === ARITHMATIC_COMMANDS.AND
    ) {
      code = [
        '@SP',
        'M=M-1',
        'A=M',
        'D=M',

        '@SP',
        'M=M-1',
        'A=M',
        `M=M${ARITHMATIC_COMMANDS_SIGN[command]}D`,

        '@SP', // push 之后往前移动一位
        'M=M+1'
      ];
    } else if (
      command === ARITHMATIC_COMMANDS.EQ ||
      command === ARITHMATIC_COMMANDS.GT ||
      command === ARITHMATIC_COMMANDS.LT
    ) {
      const labelMap = {
        [ARITHMATIC_COMMANDS.EQ]: 'EQ',
        [ARITHMATIC_COMMANDS.GT]: 'GT',
        [ARITHMATIC_COMMANDS.LT]: 'LT'
      };
      const jumpMap = {
        [ARITHMATIC_COMMANDS.EQ]: 'D;JEQ',
        [ARITHMATIC_COMMANDS.GT]: 'D;JGT',
        [ARITHMATIC_COMMANDS.LT]: 'D;JLT'
      };
      const trueLabel = labelMap[command] + this.jumpIndex;
      const jump = jumpMap[command];
      this.jumpIndex++;
      code = [
        '@SP',
        'M=M-1',
        'A=M',
        'D=M',

        '@SP',
        'M=M-1',
        'A=M',
        'D=M-D',
        '@R13',
        'M=-1',
        `@${trueLabel}`,
        jump,
        '@R13',
        'M=0',

        `(${trueLabel})`,
        '@R13',
        'D=M',
        '@SP',
        'A=M',
        'M=D',

        '@SP',
        'M=M+1'
      ];
    } else if (command === ARITHMATIC_COMMANDS.NEG || command === ARITHMATIC_COMMANDS.NOT) {
      code = ['@SP', 'D=M-1', 'A=D', `M=${ARITHMATIC_COMMANDS_SIGN[command]}M`];
    }
    this.cache.push(...code);
  }

  writePushPop(command, segment, index) {
    if (command === COMMANDS.C_PUSH) {
      this.cache.push(...this._getComment('push', segment, index));
      const code = [...this._getValutToDRegister(command, segment, index), ...this._getPushCode('D')];
      this.cache.push(...code);
    } else if (command === COMMANDS.C_POP) {
      this.cache.push(...this._getComment('pop', segment, index));
      const code = this._getPopCode(segment, index);
      this.cache.push(...code);
    }
  }

  writeLabel(label) {
    this.cache.push(...this._getComment('label', label));
    const code = [`(${this._getAssemblyLabel(label)})`];
    this.cache.push(...code);
  }

  writeGoto(label) {
    this.cache.push(...this._getComment('goto', label));
    const code = [`@${this._getAssemblyLabel(label)}`, '0;JMP'];
    this.cache.push(...code);
  }

  writeIf(label) {
    this.cache.push(...this._getComment('if-goto', label));
    const code = ['@SP', 'M=M-1', 'A=M', 'D=M', `@${this._getAssemblyLabel(label)}`, 'D;JNE'];
    this.cache.push(...code);
  }

  writeFunction(functionName, nVars) {
    this.cache.push(...this._getComment('function', functionName, nVars));
    const code = [`(${functionName})`];
    // push 0, nVars-times
    const pushConst0ToSp = ['@SP', 'A=M', 'M=0', '@SP', 'M=M+1'];
    for (let i = 0; i < nVars; i++) {
      code.push(...pushConst0ToSp);
    }
    this.cache.push(...code);
    // this.functionCallStack.push(functionName);
    this.functionCallStack[0] = functionName;
  }

  writeCall(functionName, nArgs) {
    this.cache.push(...this._getComment('call', functionName, nArgs));
    const pushDToSp = ['@SP', 'A=M', 'M=D', '@SP', 'M=M+1'];
    const argOffset = parseInt(nArgs) + 5;
    const returnAddressKey = this.functionCallStack.length ? this.functionCallStack.join('.') : 'main';
    if (typeof this.returnAddressIndexMap[returnAddressKey] === 'undefined') {
      this.returnAddressIndexMap[returnAddressKey] = 0;
    }
    const index = this.returnAddressIndexMap[returnAddressKey]++;
    const returnAddressLabel = `${returnAddressKey}$ret.${index}`;

    const code = [
      // push returnAddress
      `@${returnAddressLabel}`,
      'D=A',
      ...pushDToSp,

      // push LCL
      '@LCL',
      'D=M',
      ...pushDToSp,

      // push ARG
      '@ARG',
      'D=M',
      ...pushDToSp,

      // push THIS
      '@THIS',
      'D=M',
      ...pushDToSp,

      // push THAT
      '@THAT',
      'D=M',
      ...pushDToSp,

      // ARG = SP-5-nArgs 此处nArgs是2
      `@${argOffset}`,
      'D=A',
      '@SP',
      'D=M-D',

      '@ARG',
      'M=D',

      // LCL = SP
      '@SP',
      'D=M',
      '@LCL',
      'M=D',

      // goto f
      `@${functionName}`,
      '0;JMP',

      // (returnAddress label)
      `(${returnAddressLabel})`
    ];
    this.cache.push(...code);
  }

  writeReturn() {
    this.cache.push(...this._getComment('return'));
    const code = [
      // frame = LCL, frame is a temp variable
      '@LCL',
      'D=M',
      '@R13',
      'M=D', // R13暂存下了@LCL的M 可以理解为frame的终点位置

      // returnAddress = *(frame-5)
      '@5',
      'D=A',
      '@R13',
      'A=M-D',
      'D=M',
      '@R14',
      'M=D', // R14暂存下了returnAddress

      // *ARG = pop()
      '@SP',
      'M=M-1',
      'A=M',
      'D=M',
      '@ARG',
      'A=M',
      'M=D',

      // SP = ARG + 1
      '@ARG',
      'D=M',
      '@SP',
      'M=D+1',

      // THAT = *(frame-1)
      '@1',
      'D=A',
      '@R13',
      'A=M-D',
      'D=M',
      '@THAT',
      'M=D',

      // THIS = *(frame-2)
      '@2',
      'D=A',
      '@R13',
      'A=M-D',
      'D=M',
      '@THIS',
      'M=D',

      // ARG = *(frame-3)
      '@3',
      'D=A',
      '@R13',
      'A=M-D',
      'D=M',
      '@ARG',
      'M=D',

      // LCL = *(frame-4)
      '@4',
      'D=A',
      '@R13',
      'A=M-D',
      'D=M',
      '@LCL',
      'M=D',

      // goto returnAddress
      '@R14',
      'A=M',
      '0;JMP'
    ];
    this.cache.push(...code);
    // this.functionCallStack.pop();
  }

  _getAssemblyLabel(label) {
    const functionName = this.functionCallStack.length ? this.functionCallStack.join('.') : 'main';
    return `${functionName}$${label}`;
  }

  _getComment(command, segment = '', index = '') {
    return [`// ${command} ${segment} ${index}`.trim()];
  }

  _getValutToDRegister(command, segment, index) {
    if (segment === SEGMENTS.CONSTANT) {
      return [`@${index}`, 'D=A'];
    }
    if (segment === SEGMENTS.POINTER) {
      return [`@${POINTER_SEG_MAP[index]}`, 'D=M'];
    }
    if (segment === SEGMENTS.STATIC) {
      return [`@${this.fileName}.${index}`, 'D=M'];
    }
    const ramAddressBase = SEG_TO_RAM[segment];
    return [`@${index}`, 'D=A', `@${ramAddressBase}`, 'A=M+D', 'D=M'];
  }

  _getPushCode(register) {
    return ['@SP', 'A=M', `M=${register}`, '@SP', 'M=M+1'];
  }

  _getPopCode(segment, index) {
    if (segment === SEGMENTS.TEMP) {
      const base = 5;
      const seg = base + index;
      return [
        '@SP',
        'M=M-1',
        'A=M',
        'D=M',

        // pop出来的value 放入R13
        '@R13',
        'M=D',

        // segment-index的地址 放入R14
        `@${seg}`,
        'D=A',
        '@R14',
        'M=D',

        // 用R14找到segment-index的地址所在 然后把R13的数据放入
        '@R13',
        'D=M',
        '@R14',
        'A=M',
        'M=D'
      ];
    } else if (segment === SEGMENTS.POINTER) {
      return [
        '@SP',
        'M=M-1',
        'A=M',
        'D=M', // pop出来的value 放入D

        `@${POINTER_SEG_MAP[index]}`,
        'M=D' // 放入THAT or THAT
      ];
    } else if (segment === SEGMENTS.STATIC) {
      return [
        '@SP',
        'M=M-1',
        'A=M',
        'D=M', // pop出来的value

        `@${this.fileName}.${index}`,
        'M=D'
      ];
    } else {
      const seg = SEG_TO_RAM[segment];
      return [
        '@SP',
        'M=M-1',
        'A=M',
        'D=M',

        // pop出来的value 放入R13
        '@R13',
        'M=D',

        // segment-index的地址 放入R14
        `@${index}`,
        'D=A',
        `@${seg}`,
        'D=M+D', // segment-index的地址
        '@R14',
        'M=D',

        // 用R14找到segment-index的地址所在 然后把R13的数据放入
        '@R13',
        'D=M',
        '@R14',
        'A=M',
        'M=D'
      ];
    }
  }

  _getEndCode() {
    return ['// END', '(END)', '@END', '0;JMP'];
  }

  _addEnd() {
    this.cache.push(...this._getEndCode());
  }

  close() {
    this._addEnd();
  }
}

module.exports.CodeWriter = CodeWriter;
